package org.chipu.jnids.sancp;

import java.io.File;
import java.util.*;
import java.util.logging.Level;
import org.chipu.jnids.beans.Flow;
import org.chipu.jnids.beans.Profile;
import org.chipu.jnids.controller.DBLayer;
import org.chipu.jnids.display.TextConsole;
import org.chipu.jnids.sce.profile.DateAsNumber;

public final class ProfileCreatorDb extends ProfileCreator implements LogFileProcessor {
	/**
	 * Necessary because we calculate in and out at the same time, but the parent class makes 2 separate calls
	 */
	private transient Set<Flow> connectionsOut;

	public ProfileCreatorDb(DBLayer db, SancpManager sancp) {
		super(db, sancp);
	}

	/**
	 * Store the SANCP data into the database.<br>
	 * Use that data to create the profile<br>
	 * Cannot run in the EDT
	 */
	@Override
	void process(final Profile p) throws Exception {
		// put the data in the database
		for(File f : sancp.getSancpStatsLogFiles())
			NetFlowReader.getInstance().processFile("stats", this, f);
		logger.info("SANCP data stored in the database at " + System.currentTimeMillis());
		// create the profile
		super.process(p);
	}

	// IMPROVE use transaction
	private transient StringBuilder halfSqlFillerQuery;
	private transient String[] fields;

	@Override
	public void startFilling(String[] pattern, Map<String, Class<?>> types) throws Exception {
		dbController.recreateSancpTable(pattern, types);

		// create the prefix for the fill query
		halfSqlFillerQuery = new StringBuilder();
		halfSqlFillerQuery.append("INSERT INTO ").append(DBLayer.initialDataTable).append(" (");
		for(String key : pattern)
			halfSqlFillerQuery.append(key).append(',');
		halfSqlFillerQuery.replace(halfSqlFillerQuery.length() - 1, halfSqlFillerQuery.length(), "");
		halfSqlFillerQuery.append(") VALUES (");

		fields = pattern;
		TextConsole.getInstance().startProgress("ProfileCreatorDb", "Storing sancp data in the database");
	}

	@Override
	public void fill(Map<String, Number> data) throws Exception {
		assert halfSqlFillerQuery != null;
		final StringBuilder sqlb = new StringBuilder(halfSqlFillerQuery);
		for(String field : fields) {
			Number value = data.get(field);
			sqlb.append(value == null? "0": value instanceof DateAsNumber? " FROM_UNIXTIME(" + value.intValue() + ") ": value)
				.append(',');
		}
		sqlb.replace(sqlb.length() - 1, sqlb.length(), "");
		sqlb.append(')');
		try {
			dbController.addSancpData(sqlb.toString());
		} catch(Exception ex) {
			logger.log(Level.SEVERE, "Query: " + sqlb + "\nData to fill:\t" + data, ex);
			throw ex;
		}
		TextConsole.getInstance().doProgress("ProfileCreatorDb");
	}

	@Override
	public void nextFile(File f) {
		TextConsole.getInstance().doStep("ProfileCreatorDb", f.getName());
	}

	@Override
	public void endFilling() throws Exception {
		dbController.createSancpTimeIndex();
		halfSqlFillerQuery = null;
		TextConsole.getInstance().endProgress("ProfileCreatorDb");
	}

	// -----

	@Override
	protected int getInitTime() throws Exception {
		return dbController.getTimeOfFirstSancpFlow();
	}

	@Override
	protected int getEndTime() throws Exception {
		return dbController.getTimeOfLastSancpFlow();
	}

	@Override
	protected Collection<Flow> getConnectionsInAt(int time) throws Exception {
		Set<Flow> connectionsIn = new HashSet<Flow>(snapshotConnectionSize);
		connectionsOut = new HashSet<Flow>(snapshotConnectionSize);
		List<Flow> relevantFlows = dbController.getFlowsAt(time);
		int ii = relevantFlows.size();
		for(int i = 0; i < ii; i++) {
			// much faster than enhanced loop
			Flow f = relevantFlows.get(i);
			// PERFORMANCE check which type of flow is the most common, and which checking is the
			// most expensive, and reorder the checks
			boolean fromLAN = f.isFromInside();
			boolean toLAN = f.isToInside();
			if (!fromLAN && !toLAN) {
				logger.severe("Analyzing data from a different network. Please set manually the correct network");
				throw new IllegalStateException("The package neither comes from LAN nor is sent to LAN");
			}
			if (fromLAN && toLAN) {/**/
			} else if (fromLAN)
				connectionsIn.add(f);
			else
				connectionsOut.add(f);
		}
		return connectionsIn;
	}

	/** Must be called just after #getConnectionsInAt() */
	@Override
	protected Collection<Flow> getConnectionsOutAt(int time) {
		return connectionsOut;
	}
}